package com.asdhawaii.otello.players;

import com.asdhawaii.otello.board.Direction;
import com.asdhawaii.otello.board.Move;
import com.asdhawaii.otello.board.Square;
import com.asdhawaii.otello.board.SquareContents;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Contains various heuristics for finding the "best" move for an Otello AI to make.
 * 
 * Aside from the random method, all of the heuristics should return a collection of moves
 * rather than a specific move.  This is so that the AI may apply other heuristics to the moves.
 * 
 * @author Branden Ogata
 *
 */

public class AiStrategies
{
  /**
   * The logger for this AiStrategies class.
   * 
   */
  private static Logger logger = LoggerFactory.getLogger(AiStrategies.class.getName());
  
  /**
   * Indicates if a move in a particular direction is valid.
   * 
   * @param board The Square[][] representing the board.
   * @param row The int equal to the row coordinate of the current Square to examine.
   * @param column The int equal to the column coordinate of the current Square to examine.
   * @param direction The Direction to move in.
   * @param stack The String representing the pieces thus far in this line of inquiry.
   * @return A boolean that is true if the move is valid,
   *                           false otherwise.
   *                           
   */
  
  protected static final boolean isValidMove(Square[][] board, int row, int column, 
                                             Direction direction, String stack)
  {
    boolean valid = false;
    
    // If the coordinates are invalid or the square is empty, then the move is invalid
    if ((row < 0) || (board.length <= row) || (column < 0) || (board[row].length <= column) || 
        (board[row][column].getContents().equals(SquareContents.EMPTY)))
    {
      valid = false;
    }
    // If the square is the same as the start of the stack 
    else if ((board[row][column].getContents().toString().equals(stack.substring(0, 1))))   
    {
      // If opposing pieces make up the rest of the stack then the flip is valid
      valid = !(stack.substring(1).contains(stack.substring(0, 1)) || (stack.length() < 2)); 
//      System.out.println(stack);
    }
    // Else continue recursion
    else
    {
      valid = isValidMove(board, row + direction.getDeltaRows(), 
                          column + direction.getDeltaColumns(), direction, 
                          stack + board[row][column].getContents().toString());
    }
    
    return valid;
  }

  /**
   * Returns the number of flipped pieces on the given board going in the given Direction.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param row The int equal to the y coordinate of the Square to examine.
   * @param column The int equal to the x coordinate of the Square to examine.
   * @param direction The Direction equal to the direction in which to travel.
   * @param color The String representing the color of the piece to place.
   * @return An int equal to the number of flipped enemy pieces resulting from the move.
   * 
   */
  
  protected static int getFlips(Square[][] board, int row, int column, 
                                Direction direction, SquareContents color)
  {
    int flips = 0;
    
    if (AiStrategies.isValidMove(board, row, column, direction, color.toString()))
    {
      flips = 1 + getFlips(board, row + direction.getDeltaRows(), 
                           column + direction.getDeltaColumns(), direction, color);
    }
    
    return flips;
  }  
  
  /**
   * Returns a copy of the given board.
   * 
   * @param board The Square[][] representing the Otello board.
   * 
   * @return boardCopy A Square[][] with the same contents as the parameter.
   * 
   */
  
  private static Square[][] copyBoard(Square[][] board)
  {
    Square[][] boardCopy = new Square[board.length][board.length];
    
    for (int i = 0; i < board.length; i++)
    {
      for (int j = 0; j < board[i].length; j++)
      {
        boardCopy[i][j] = new Square(board[i][j].getContents());
      }      
    }
    
    return boardCopy;
  }
  
  /**
   * Randomly selects from the list of moves.
   * 
   * @param moves The List<Move> containing the valid moves on the board.
   * 
   * @return A Move representing the selected move.
   * 
   */
  
  public static Move random(List<Move> moves)
  {
    Random random = new Random();
    return (moves.isEmpty()) ? (null) : (moves.get(random.nextInt(moves.size())));    
  }
  
  /**
   * Returns the moves that flip the most pieces.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The String representing the color of the piece to place.
   * 
   * @return candidates A List<Move> representing the selected moves.
   * 
   */
  
  public static List<Move> maxima(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    int mostFlips = 0;
    
    // For each legal move
    for (Move move : moves)
    {
      int currentFlips = 0;
      
      // Find the number of flipped pieces in all directions
      for (Direction d : Direction.values())
      {
        currentFlips += AiStrategies.getFlips(board, move.getRow() + d.getDeltaRows(), 
                                              move.getColumn() + d.getDeltaColumns(), d, color);
      }

      // If the number of current flips is greater than the previous most flips,
      // then update the selection
      if (currentFlips > mostFlips)
      {
        mostFlips = currentFlips;
        candidates.clear();
        candidates.add(move);
      }
      // If the number of current flips is equal to the previous most flips,
      // then add to the selection candidates
      else if (currentFlips == mostFlips)
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }
  
  /**
   * Selects any corner moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getCorners(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isCorner())
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }
  
  /**
   * Selects any A square moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getASquares(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isASquare())
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }
  
  /**
   * Selects any C square moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getCSquares(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isCSquare())
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }  
  
  /**
   * Selects any X square moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getXSquares(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isXSquare())
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }  
  
  /**
   * Selects any contiguous C square moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getContiguousCSquares(Square[][] board, List<Move> moves, 
                                                 SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();

    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isCSquare())
      {
        if (move.getRow() == 0)
        {
          if ((move.getColumn() == 1) && (board[0][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 6) && (board[0][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
        else if (move.getRow() == 1)
        {
          if ((move.getColumn() == 0) && (board[0][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 7) && (board[0][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
        else if (move.getRow() == 6)
        {
          if ((move.getColumn() == 0) && (board[7][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 7) && (board[7][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
        else if (move.getRow() == 7)
        {
          if ((move.getColumn() == 1) && (board[7][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 6) && (board[7][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
      }
    }
  
    return candidates;
  }  
  
  /**
   * Selects any contiguous X square moves that exist.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> containing the move or moves to consider.
   * 
   */
  
  public static List<Move> getContiguousXSquares(Square[][] board, List<Move> moves, 
                                                 SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      if (board[move.getRow()][move.getColumn()].isXSquare())
      {
        if (move.getRow() == 1)
        {
          if ((move.getColumn() == 1) && (board[0][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 6) && (board[0][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
        else if (move.getRow() == 6)
        {
          if ((move.getColumn() == 1) && (board[7][0].getContents().equals(color)))
          {
            candidates.add(move);
          }
          else if ((move.getColumn() == 6) && (board[7][7].getContents().equals(color)))
          {
            candidates.add(move);
          }
        }
      }
    }
    
    return candidates;
  }  
  
  /**
   * Selects the moves that flips the fewest number of pieces.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> representing the selected moves.
   * 
   */
  
  public static List<Move> minima(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    int fewestFlips = 64;
    
    // Find the move or moves with the fewest flips
    for (Move move : moves)
    {
      int currentFlips = 0;
      
      for (Direction d : Direction.values())
      {
        currentFlips += AiStrategies.getFlips(board, move.getRow() + d.getDeltaRows(), 
                                              move.getColumn() + d.getDeltaColumns(), d, color);
        
      }
      
      if (currentFlips < fewestFlips)
      {
        fewestFlips = currentFlips;
        candidates.clear();
        candidates.add(move);
      }
      else if (currentFlips == fewestFlips)
      {
        candidates.add(move);
      }
    }
    
    return candidates;
  }
  
  /**
   * Selects the moves that place a piece that the opponent cannot flip.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> representing the selected moves.
   * 
   */
  
  public static List<Move> stableMoves(Square[][] board, List<Move> moves, SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    
    for (Move move : moves)
    {
      // Assume that the move is valid (and attempt to disprove later)
      candidates.add(move);
      
      Map<Direction, SquareContents> stability = new HashMap<Direction, SquareContents>();
      Square[][] boardCopy = AiStrategies.copyBoard(board);
      boardCopy[move.getRow()][move.getColumn()].setContents(color);
      
      // Go until a stopping point in each direction
      for (Direction d : Direction.values())
      {
        int flips = AiStrategies.getFlips(board, move.getRow() + d.getDeltaRows(), 
                                          move.getColumn() + d.getDeltaColumns(), 
                                          d, color);
        
        // Flip the pieces that this move would make
        for (int i = 0; i < flips; i++)
        {
          Move flipped = new Move(move.getRow() + (d.getDeltaRows() * (i + 1)), 
                                  move.getColumn() + (d.getDeltaColumns() * (i + 1)));
          boardCopy[flipped.getRow()][flipped.getColumn()].setContents(color);
          
          // Check each flipped piece to determine if it can itself be flipped
          boolean canFlip = false;
          
          for (Direction flipDirection : Direction.values())
          {
            canFlip |= AiStrategies.isValidMove(boardCopy, 
                                                flipped.getRow(), flipped.getColumn(), 
                                                flipDirection, color.opposite().toString());
          }
          
          // If the opponent can flip this flipped piece,
          // then an opposing piece could end up in this direction of the original move
          if (canFlip)
          {
            stability.put(d, color.opposite());
          }
        }
        
        // If the stability is not already decided, then continue in the same direction
        if (!stability.containsKey(d))
        {
          // Check the square after any flipped pieces
          Move nextSquare = new Move(move.getRow() + (d.getDeltaRows() * flips),
                                     move.getColumn() + (d.getDeltaColumns() * flips));
          
          while (((0 <= nextSquare.getRow()) && 
                  (nextSquare.getRow() < boardCopy.length)) && 
                 ((0 <= nextSquare.getColumn()) && 
                  (nextSquare.getColumn() < boardCopy[0].length)) &&
                 (boardCopy[nextSquare.getRow()]
                           [nextSquare.getColumn()].getContents().equals(color)))
          {
            nextSquare = new Move(nextSquare.getRow() + d.getDeltaRows(),
                                  nextSquare.getColumn() + d.getDeltaColumns());
            
            if (Move.onBoard(boardCopy, nextSquare))
            {
              stability.put(d, boardCopy[nextSquare.getRow()]
                                        [nextSquare.getColumn()].getContents());
            }
          }
        }
      }
      
      // Now that the stability has been evaluated in all directions, evaluate the original move
      for (Direction d : Direction.values())
      {
        // If the opponent has or can have a piece on one side 
        // and an empty space on the other side,
        // then the move is not stable
        if (((color.opposite().equals(stability.get(d)) || 
             (SquareContents.EMPTY.equals(stability.get(d))))) && 
            (SquareContents.EMPTY.equals(stability.get(d.opposite()))))
        {
          candidates.remove(move);
        }
      }
    }
    
    return candidates;
  }
  
  /**
   * Select the moves that flip the most pieces that the opponent cannot flip.
   * 
   * @param board The Square[][] representing the Otello board.
   * @param moves The List<Move> containing the valid moves on the board.
   * @param color The SquareContents representing the color of the piece to place.
   * 
   * @return candidates A List<Move> representing the selected moves.
   * 
   */
  
  public static List<Move> partialStableMoves(Square[][] board, List<Move> moves, 
                                              SquareContents color)
  {
    List<Move> candidates = new ArrayList<Move>();
    int maxStablePieces = 0;

    for (Move move : moves)
    {
      int stablePieces = 0;
      
      Map<Direction, SquareContents> stability = new HashMap<Direction, SquareContents>();
      Square[][] boardCopy = AiStrategies.copyBoard(board);
      boardCopy[move.getRow()][move.getColumn()].setContents(color);
      
      // Go until a stopping point in each direction
      for (Direction d : Direction.values())
      {
        int flips = AiStrategies.getFlips(board, move.getRow() + d.getDeltaRows(), 
                                          move.getColumn() + d.getDeltaColumns(), 
                                          d, color);
        
        // Flip the pieces that this move would make
        for (int i = 0; i < flips; i++)
        {
          Move flipped = new Move(move.getRow() + (d.getDeltaRows() * (i + 1)), 
                                  move.getColumn() + (d.getDeltaColumns() * (i + 1)));
          boardCopy[flipped.getRow()][flipped.getColumn()].setContents(color);
          
          Map<Direction, Boolean> flipStability = new HashMap<Direction, Boolean>();
          
          // Check each flipped piece to determine if it can itself be flipped
          for (Direction flipDirection : Direction.values())
          {
            int row = Math.min(Math.max(flipped.getRow() + flipDirection.getDeltaRows(), 0), 
                               boardCopy.length - 1);
            int column = Math.min(Math.max(flipped.getColumn() + flipDirection.getDeltaColumns(), 
                                           0),
                                  boardCopy[row].length - 1);
            SquareContents flipContents = boardCopy[row][column].getContents();
            
            flipStability.put(flipDirection, 
                              AiStrategies.isValidMove(boardCopy, 
                                                       flipped.getRow(), 
                                                       flipped.getColumn(), 
                                                       flipDirection, 
                                                       color.opposite().toString()) ||
                              SquareContents.EMPTY.equals(flipContents));
          }
          
          boolean canFlip = false;
          
          for (Map.Entry<Direction, Boolean> entry : flipStability.entrySet())
          {
            canFlip |= (entry.getValue() && flipStability.get(entry.getKey().opposite()));
          }
          
          // If the opponent can flip this flipped piece,
          // then an opposing piece could end up in this direction of the original move
          if (canFlip)
          {
            stability.put(d, color.opposite());
          }
          // Else the piece is stable
          else
          {
            stablePieces++;
          }
        }
        
        // If the stability is not already decided, then continue in the same direction
        if (!stability.containsKey(d))
        {
          // Check the square after any flipped pieces
          Move nextSquare = new Move(move.getRow() + (d.getDeltaRows() * flips),
                                     move.getColumn() + (d.getDeltaColumns() * flips));
          
          while (((0 <= nextSquare.getRow()) && 
                  (nextSquare.getRow() < boardCopy.length)) && 
                 ((0 <= nextSquare.getColumn()) && 
                  (nextSquare.getColumn() < boardCopy[0].length)) &&
                 (boardCopy[nextSquare.getRow()]
                           [nextSquare.getColumn()].getContents().equals(color)))
          {
            nextSquare = new Move(nextSquare.getRow() + d.getDeltaRows(),
                                  nextSquare.getColumn() + d.getDeltaColumns());
            
            if (Move.onBoard(boardCopy, nextSquare))
            {
              stability.put(d, boardCopy[nextSquare.getRow()]
                                        [nextSquare.getColumn()].getContents());
            }
          }
        }
      }
      
      // Now that the stability has been evaluated in all directions, evaluate the original move
      boolean stableMove = true;
      
      for (Direction d : Direction.values())
      {
        // If the opponent has or can have a piece on one side 
        // and an empty space on the other side,
        // then the move is not stable
        if (((color.opposite().equals(stability.get(d)) || 
             (SquareContents.EMPTY.equals(stability.get(d))))) && 
            (SquareContents.EMPTY.equals(stability.get(d.opposite()))))
        {
          stableMove = false;
        }
      }
      
      if (stableMove)
      {
        stablePieces++;
      }
      
      // Determine if this move gives away a corner
      boolean givesAwayCorner = false;
      
      // (0,0) corner
      if (SquareContents.EMPTY.equals(boardCopy[0][0].getContents()))
      {
        // If the move is on or flips an X square, then simpler to assume that it gives away corner
        if (((move.getRow() == 1) && (move.getColumn() == 1)) || 
            ((!(SquareContents.EMPTY.equals(boardCopy[1][1].getContents()))) && 
             (boardCopy[1][1].getContents().opposite().equals(board[1][1].getContents()))))
        {
          givesAwayCorner = true;
          logger.debug("Move {} gives away (0,0) through X square", move);
        }
        
        // If the move is on a C square
        else if ((move.getRow() == 0) && (move.getColumn() == 1))
        {
          // Check along row
          for (int j = 2; j < boardCopy[0].length; j++)
          {
            if (!color.equals(boardCopy[0][j].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (0,0) through C square", move);
            }
          }
        }
        else if ((move.getRow() == 1) && (move.getColumn() == 0))
        {
          // Check along column
          for (int k = 2; k < boardCopy.length; k++)
          {
            if (!color.equals(boardCopy[k][0].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (0,0) through C square", move);
            }
          }
        }
      }
      
      // (0,7) corner
      if (SquareContents.EMPTY.equals(boardCopy[0][7].getContents()))
      {
        // If the move is on or flips an X square, then simpler to assume that it gives away corner
        if (((move.getRow() == 1) && (move.getColumn() == 6)) || 
            ((!(SquareContents.EMPTY.equals(boardCopy[1][6].getContents()))) && 
             (boardCopy[1][6].getContents().opposite().equals(board[1][6].getContents()))))
        {
          givesAwayCorner = true;
          logger.debug("Move {} gives away (0,7) through X square", move);
        }
        
        // If the move is on a C square
        else if ((move.getRow() == 0) && (move.getColumn() == 6))
        {
          // Check along row
          for (int j = 5; j >= 0; j--)
          {
            if (!color.equals(boardCopy[0][j].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (0,7) through C square", move);
            }
          }
        }
        else if ((move.getRow() == 1) && (move.getColumn() == 7))
        {
          // Check along column
          for (int k = 2; k < boardCopy.length; k++)
          {
            if (!color.equals(boardCopy[k][7].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (0,7) through C square", move);
            }
          }
        }
      }
      
      // (7,0) corner
      if (SquareContents.EMPTY.equals(boardCopy[7][0].getContents()))
      {
        // If the move is on or flips an X square, then simpler to assume that it gives away corner
        if (((move.getRow() == 6) && (move.getColumn() == 1)) || 
            ((!(SquareContents.EMPTY.equals(boardCopy[6][1].getContents()))) && 
             (boardCopy[6][1].getContents().opposite().equals(board[6][1].getContents()))))
        {
          givesAwayCorner = true;
          logger.debug("Move {} gives away (7,0) through X square", move);          
        }
        
        // If the move is on a C square
        else if ((move.getRow() == 7) && (move.getColumn() == 1))
        {
          // Check along row
          for (int j = 2; j < boardCopy[7].length; j++)
          {
            if (!color.equals(boardCopy[7][j].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (7,0) through C square", move);          
            }
          }
        }
        else if ((move.getRow() == 6) && (move.getColumn() == 0))
        {
          // Check along column
          for (int k = 5; k >= 0; k--)
          {
            if (!color.equals(boardCopy[k][0].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (7,0) through C square", move);          
            }
          }
        }        
      }
      
      // (7,7) corner
      if (SquareContents.EMPTY.equals(boardCopy[7][7].getContents()))
      {
        // If the move is on or flips an X square, then simpler to assume that it gives away corner
        if (((move.getRow() == 6) && (move.getColumn() == 6)) || 
            ((!(SquareContents.EMPTY.equals(boardCopy[6][6].getContents()))) && 
             (boardCopy[6][6].getContents().opposite().equals(board[6][6].getContents()))))
        {
          givesAwayCorner = true;
          logger.debug("Move {} gives away (7,7) through X square", move);
        }
        
        // If the move is on a C square
        else if ((move.getRow() == 7) && (move.getColumn() == 6))
        {
          // Check along row
          for (int j = 5; j >= 0; j--)
          {
            if (!color.equals(boardCopy[7][j].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (7,7) through C square", move);
            }
          }
        }
        else if ((move.getRow() == 6) && (move.getColumn() == 7))
        {
          // Check along column
          for (int k = 5; k >= 0; k--)
          {
            if (!color.equals(boardCopy[k][7].getContents()))
            {
              givesAwayCorner = true;
              logger.debug("Move {} gives away (7,7) through C square", move);
            }
          }
        }        
      }
      
      // Consider the move only if it does not give away a corner
      if (!givesAwayCorner)
      {
        // If this move flips more stable pieces than the current maximum stable pieces,
        // then clear the list of candidates and add this move
        if (stablePieces > maxStablePieces)
        {
          candidates.clear();
          candidates.add(move);
          maxStablePieces = stablePieces;
        }
        // If this move flips the same number of stable pieces as the current maximum stable pieces,
        // then add this move to the list of candidates
        else if (stablePieces == maxStablePieces)
        {
          candidates.add(move);
        }
      }
    }
    
    return candidates;
  }
}